/**
 * The contents of this file are subject to the license and copyright detailed in the LICENSE and NOTICE files at the root of the source tree and available online at
 * http://www.askosi.org/license.html
 */
package be.destin.skos.search;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.Set;
import java.util.StringTokenizer;
import java.util.TreeMap;

import org.apache.log4j.Logger;

import be.destin.skos.core.Concept;
import be.destin.skos.core.SchemeInterface;
import be.destin.skos.core.SkosManager;
import be.destin.skos.core.Term;
import be.destin.skos.core.TermList;
import be.destin.util.Util;

public class SearchUtil {

	private static final Logger log = Logger.getLogger(SearchUtil.class);

	/*
	 * Check if all words of NAME are somewhere in line within QUERY
	 * Match quality = size of NAME / size of QUERY 
	 */
	static boolean wordSentenceMatch(LinkedList<String> QUERY, LinkedList<String> NAME) {
		String firstName = NAME.getFirst();
		if (firstName==null || firstName.isEmpty()) return false;
		boolean good = false;
		String qw;
		int posQ = 0;
		while ((QUERY.size()-posQ) >= NAME.size()) {
			Iterator<String> q = QUERY.iterator();
			int curQ = 0;
			while (q.hasNext()) {
				qw = q.next();
				if ( qw.equals(firstName) && (curQ >= posQ) ) {
					good = true;
					curQ++;
					break;
				}
				curQ++;
			}
			if (!good) return false;
			posQ = curQ;
			Iterator<String> n = NAME.iterator();
			String nw = n.next();
			while (q.hasNext() && n.hasNext()) {
				qw = q.next();
				nw = n.next();
				if ( ! qw.equals(nw) ) {
					good = false;
					break;
				}
			}
			if (n.hasNext()) {
				good = false;
			}
			if (good) break;
		}
		return good;
	}

	/*
	 * Check if all words of QUERY are somewhere in line within NAME
	 * Match quality = size of QUERY / size of NAME 
	 */
	static boolean wordMatch(LinkedList<String> QUERY, LinkedList<String> NAME, boolean mustReachEnd) {
		Iterator<String> q = QUERY.iterator();
		Iterator<String> n = NAME.iterator();
		boolean good = true;
		while (q.hasNext() && n.hasNext()) {
			String qw = q.next();
			String nw = n.next();
			if ( ! qw.equals(nw) ) {
				good = false;
				break;
			}
		}
		if (!good) return false;
		if (n.hasNext() && mustReachEnd) return false;
		return true;
	}

	/*
	 * Check if the different patterns are somewhere (in order) within TOMATCH
	 */
	static boolean patternMatch(String TOMATCH, String[] starPATTERN, boolean mustReachEnd) {
		int fromIndex = 0;
		int posPAT = 0;
		for (int i = 0; i < starPATTERN.length; i++) {
			posPAT = TOMATCH.indexOf(starPATTERN[i], fromIndex);
			if (posPAT < 0) break;
			fromIndex = posPAT+starPATTERN[i].length();
		}
		if (posPAT < 0) return false;
		if (mustReachEnd && fromIndex < TOMATCH.length()) return false;
		return true;
	}

	/*
	 * Check if the different patterns are somewhere (in order) within TOMATCH (from the beginning)
	 */
	static SearchResult startsWithMatch(String PREFIX, String[] starPATTERN, int querySize, String userLanguage, boolean strictLanguage, Concept aConcept) {
		String prefName = aConcept.getPrefLabel(userLanguage, strictLanguage);
		if (prefName != null && (prefName.length() > 0)) {
			String ANAME = Util.noAccent(prefName.toUpperCase());
			if (ANAME.startsWith(PREFIX)) {
				if (starPATTERN == null)
					return new SearchResult(prefName,null,null,null,aConcept.getScheme_About(), (float)querySize/(float)prefName.length());
				else {
					if (patternMatch(ANAME.substring(PREFIX.length()),starPATTERN, false))
						return new SearchResult(prefName,null,null,null,aConcept.getScheme_About(), (float)querySize/(float)prefName.length());
				}
			}
		}
		for (Term name: aConcept.getLabel()) {
			String aLang = name.getValue();
			if (strictLanguage && aLang != TermList.anyLanguage && aLang != "en" && !aConcept.getInScheme().getSupraLanguage().contains(aLang) ) {
				continue;
			}
			String aName = name.getValue();
			if (aName == null || (aName.length() == 0)) continue;
			String ANAME = Util.noAccent(aName.toUpperCase());
			if (ANAME.startsWith(PREFIX)) {
				boolean goodTerm = starPATTERN == null;
				if (!goodTerm) {
					goodTerm = patternMatch(ANAME.substring(PREFIX.length()),starPATTERN, false);
				}
				if (goodTerm) {
					return new SearchResult(prefName,aName,name.getLang(),null,aConcept.getScheme_About(), (float)querySize/(float)aName.length());
				}
			}
		}
		return null;
	}

	static SearchResult insideMatch(String INFIX, String[] starPATTERN, int querySize, String userLanguage, boolean strictLanguage, Concept aConcept) {
		String prefName = aConcept.getPrefLabel(userLanguage, strictLanguage);
		if (prefName != null && (prefName.length() > 0)) {
			String ANAME = Util.noAccent(prefName.toUpperCase());
			int posINFIX = ANAME.indexOf(INFIX);
			if (posINFIX >= 0) {
				if (starPATTERN == null)
					return new SearchResult(prefName,null,null,null,aConcept.getScheme_About(), (float)INFIX.length()/(float)ANAME.length());
				else {
					if (patternMatch(ANAME.substring(posINFIX+INFIX.length()),starPATTERN, false)) {
						return new SearchResult(prefName,null,null,null,aConcept.getScheme_About(), (float)querySize/(float)ANAME.length());
					}
				}
			}
		}
		for (Term name: aConcept.getLabel()) {
			String aLang = name.getValue();
			if (strictLanguage && aLang != TermList.anyLanguage && aLang != "en" && !aConcept.getInScheme().getSupraLanguage().contains(aLang) ) {
				continue;
			}
			String aName = name.getValue();
			if (aName == null || (aName.length() == 0)) continue; 
			String ANAME = Util.noAccent(aName.toUpperCase());
			int posINFIX = ANAME.indexOf(INFIX);
			if (posINFIX >= 0) {
				boolean goodTerm = starPATTERN == null;
				if (!goodTerm) {
					goodTerm = patternMatch(ANAME.substring(posINFIX+INFIX.length()),starPATTERN, false);
					if (goodTerm) {
					}
				}
				if (goodTerm) {
					return new SearchResult(prefName,aName,name.getLang(),null,aConcept.getScheme_About(), (float)querySize/(float)ANAME.length());
				}
			}
		}
		return null;
	}

	public static SearchResult singularMatch(LinkedList<String> WORDS, boolean startNailed, boolean endNailed,
			String userLanguage, boolean strictLanguage, Concept aConcept) {
		String prefName = aConcept.getPrefLabel(userLanguage, strictLanguage);
		if (prefName != null && (prefName.length() > 0)) {
			LinkedList<String> ANAME = Util.singularWordSplitter(Util.noAccent(prefName.toUpperCase()));
			if (ANAME.size() >=  WORDS.size()) {
				if (wordMatch(WORDS,ANAME,endNailed))
					return new SearchResult(prefName,null,null,null,aConcept.getScheme_About(), (float)WORDS.size()/(float)ANAME.size());
				if (!startNailed) {
					int fullLength = ANAME.size();
					ANAME.removeFirst();
					while (ANAME.size() >= WORDS.size()) {
						if (wordMatch(WORDS,ANAME,endNailed))
							return new SearchResult(prefName,null,null,null,aConcept.getScheme_About(), (float)WORDS.size()/(float)fullLength);
						ANAME.removeFirst();
					}
				}
			}
		}
		for (Term name: aConcept.getLabel()) {
			String aLang = name.getValue();
			if (strictLanguage && aLang != TermList.anyLanguage && aLang != "en" && !aConcept.getInScheme().getSupraLanguage().contains(aLang) ) {
				continue;
			}
			String aName = name.getValue();
			if (aName == null || (aName.length() == 0)) continue; 
			LinkedList<String> ANAME = Util.singularWordSplitter(Util.noAccent(aName.toUpperCase()));
			int fullLength = ANAME.size();
			boolean goodTerm = false;
			if (ANAME.size() >=  WORDS.size()) {
				if (wordMatch(WORDS,ANAME,endNailed)) goodTerm = true;
				if (!goodTerm && !startNailed) {
					ANAME.removeFirst();
					while (ANAME.size() >= WORDS.size()) {
						if (wordMatch(WORDS,ANAME,endNailed)) {
							goodTerm = true;
							break;
						}
						ANAME.removeFirst();
					}
				}
			}
			if (goodTerm) {
				return new SearchResult(prefName,aName,name.getLang(),null,aConcept.getScheme_About(), (float)WORDS.size()/(float)fullLength);
			}
		}
		return null;
	}

	static SearchResult singularSentenceMatch(LinkedList<String> WORDS, String userLanguage, boolean strictLanguage, Concept aConcept) {
		String prefName = aConcept.getPrefLabel(userLanguage, strictLanguage);
		if (prefName != null && (prefName.length() > 0)) {
			LinkedList<String> ANAME = Util.singularWordSplitter(Util.noAccent(prefName.toUpperCase()));
			if (ANAME.size() <=  WORDS.size()) {
				if (wordSentenceMatch(WORDS,ANAME))
					return new SearchResult(prefName,null,null,null,aConcept.getScheme_About(), (float)ANAME.size()/(float)WORDS.size());
			}
		}
		for (Term name: aConcept.getLabel()) {
			String aLang = name.getValue();
			if (strictLanguage && aLang != TermList.anyLanguage && aLang != "en" && !aConcept.getInScheme().getSupraLanguage().contains(aLang) ) {
				continue;
			}
			String aName = name.getValue();
			if (aName == null || (aName.length() == 0)) continue; 
			LinkedList<String> ANAME = Util.singularWordSplitter(Util.noAccent(aName.toUpperCase()));
			if (ANAME.size() <=  WORDS.size()) {
				if (wordSentenceMatch(WORDS,ANAME))  {
					return new SearchResult(prefName,aName,name.getLang(),null,aConcept.getScheme_About(), (float)ANAME.size()/(float)WORDS.size());
				}
			}
		}
		return null;
	}

	static SearchResult exactLabelMatch(String LABEL, String[] starPATTERN, int querySize, String userLanguage, boolean strictLanguage, Concept aConcept) {
		String prefName = aConcept.getPrefLabel(userLanguage, strictLanguage);
		if (prefName != null && (prefName.length() > 0)) {
			if (starPATTERN == null) {
				if (prefName.equalsIgnoreCase(LABEL))
					return new SearchResult(prefName,null,null,null,aConcept.getScheme_About(), (float)querySize/(float)prefName.length());
			} else {
				String ANAME = Util.noAccent(prefName.toUpperCase());
				if (ANAME.startsWith(LABEL)) {
					if (patternMatch(ANAME.substring(LABEL.length()),starPATTERN, true))
						return new SearchResult(prefName,null,null,null,aConcept.getScheme_About(), (float)querySize/(float)prefName.length());
				}
			}
		}
		for (Term name: aConcept.getLabel()) {
			String aLang = name.getValue();
			if (strictLanguage && aLang != TermList.anyLanguage && aLang != "en" && !aConcept.getInScheme().getSupraLanguage().contains(aLang) ) {
				continue;
			}
			String aName = name.getValue();
			if (aName == null || (aName.length() == 0)) continue; 
			boolean goodTerm = false;
			if (starPATTERN == null) {
				if (aName.equalsIgnoreCase(LABEL)) {
					goodTerm = true;
				}
			} else {
				String ANAME = Util.noAccent(aName.toUpperCase());
				if (ANAME.startsWith(LABEL)) {
					goodTerm = patternMatch(ANAME.substring(LABEL.length()),starPATTERN,true);
				}
			}
			if (goodTerm) {
				return new SearchResult(prefName,aName,name.getLang(),null,aConcept.getScheme_About(), (float)querySize/(float)aName.length());
			}
		}
		return null;
	}

	public static ArrayList<SearchResult> labelMatch(SkosManager skosManager, String query, String userLanguage, boolean strictLanguage, LabelMatchType matchType, Collection<Concept> concepts) {
		if (query == null || concepts == null || (concepts.size() == 0))
			return new ArrayList<SearchResult>();
		if (matchType == LabelMatchType.INDEPENDANT_PIECES) {
			StringTokenizer st = new StringTokenizer(query,""+SchemeInterface.pieceBoundary);
			ArrayList<SearchResult>matchResult = new ArrayList<SearchResult>();
			while (st.hasMoreTokens()) {
				String aQuery = st.nextToken();
				matchResult = labelMatch(skosManager, aQuery, userLanguage, strictLanguage, LabelMatchType.ANYWHERE, concepts);
				log.debug("Matches="+matchResult);
				if (matchResult == null || matchResult.isEmpty()) {
					return new ArrayList<SearchResult>();
				} else {
					if (st.hasMoreTokens()) {
						concepts = skosManager.getMatchingConcepts(matchResult); // context for the next match round...
					}
				}
				log.debug("Concepts="+concepts);
			}
			return matchResult;
		} else {
			TreeMap<String,SearchResult> result = new TreeMap<String,SearchResult>();
			String QUERY = "";
			String[] starPATTERN = null;
			int querySize = 1;
			boolean start_nailed = true;
			boolean end_nailed = true;
			if (matchType != LabelMatchType.EVERYTHING) {
				QUERY = Util.noAccent(query.toUpperCase()).trim();
	
				while (QUERY.length() > 0 && QUERY.charAt(0)==SchemeInterface.joker_0_n) {
					QUERY = QUERY.substring(1);
					start_nailed = false;
				}
				while (QUERY.length() > 0 && QUERY.charAt(QUERY.length()-1)==SchemeInterface.joker_0_n) {
					QUERY = QUERY.substring(0,QUERY.length()-1);
					end_nailed = false;
				}
				if (QUERY.isEmpty() && !(start_nailed && end_nailed) ) {
					matchType = LabelMatchType.EVERYTHING;
				} else {
					int posJoker = QUERY.indexOf(SchemeInterface.joker_0_n);
					if (posJoker >= 0) {
						StringTokenizer st = new StringTokenizer(QUERY.substring(posJoker),""+SchemeInterface.joker_0_n);
						starPATTERN = new String[st.countTokens()];
						if (starPATTERN.length == 0) {
							starPATTERN = null;
						} else {
							int i = 0;
							while (st.hasMoreTokens()) {
								starPATTERN[i] = st.nextToken();
								querySize += starPATTERN[i].length();
								i++;
							}
						}
						QUERY = QUERY.substring(0,posJoker);
						querySize = querySize + QUERY.length()-1;
					}
				}
			}
	
			if (matchType != LabelMatchType.EVERYTHING) {
				if (QUERY.isEmpty() && starPATTERN == null) {
					return new ArrayList<SearchResult>(result.values()); //empty
				}
			}
	
			LinkedList<String> WORDS = null;
			if (matchType == LabelMatchType.SINGULAR_WORDS || matchType == LabelMatchType.SINGULAR_SENTENCES) {
		    	WORDS = Util.singularWordSplitter(QUERY);
		    	if (WORDS == null || WORDS.isEmpty()) {
		    		return new ArrayList<SearchResult>(result.values());
		    	}
			}
	
			for (Concept aConcept: concepts) {
				SearchResult currResult = null;
				switch (matchType) {
				case EVERYTHING:
					currResult = new SearchResult(aConcept.getPrefLabel(userLanguage, strictLanguage),null,null,null,aConcept.getScheme_About(),1.0f);
					break;
				case STARTS_WITH:
					if (start_nailed) {
						currResult = startsWithMatch(QUERY,starPATTERN,querySize,userLanguage, strictLanguage, aConcept);
					} else {
						currResult = insideMatch(QUERY,starPATTERN,querySize,userLanguage, strictLanguage, aConcept);
					}
					break;
				case SINGULAR_WORDS:
					currResult = singularMatch(WORDS,start_nailed,end_nailed,userLanguage, strictLanguage, aConcept);
					break;
				case SINGULAR_SENTENCES:
					currResult = singularSentenceMatch(WORDS,userLanguage, strictLanguage, aConcept);
					break;
				case ANYWHERE:
					currResult = insideMatch(QUERY,starPATTERN,querySize,userLanguage, strictLanguage, aConcept);
					break;
				case EXACT: 
					if (end_nailed) {
						currResult = exactLabelMatch(QUERY,starPATTERN,querySize,userLanguage, strictLanguage, aConcept);
					} else {
						if (start_nailed) {
							currResult = startsWithMatch(QUERY,starPATTERN,querySize,userLanguage, strictLanguage, aConcept);
						} else {
							currResult = insideMatch(QUERY,starPATTERN,querySize,userLanguage, strictLanguage, aConcept);
						}				
					}
					break;
				}
				if (currResult == null) continue;
				String scopeNote = aConcept.getScopeNote(userLanguage);
				if (scopeNote != null && !scopeNote.isEmpty()) {
					currResult.setNote(skosManager.toStringScheme_Concept(scopeNote, userLanguage, aConcept.getInScheme(), null, null, true));
				}
				result.put(aConcept.getSortLabel(userLanguage),currResult);
			}
			return new ArrayList<SearchResult>(result.values());
		}
	}

	public static Concept[] exactMatch(Set<SchemeInterface> schemes, String value, String language, boolean strictLanguage) {
		value = value.trim();
		if (value.length() == 0) return new Concept[0];
	
		HashSet<Concept> concepts = new HashSet<Concept>();
		for (SchemeInterface aScheme: schemes) {
			Collection<SearchResult> entries = aScheme.labelMatch(value, language, strictLanguage, LabelMatchType.EXACT);
			for (SearchResult anEntry: entries)  {
				//int j = anEntry.indexOf(SchemeInterface.fieldSeparator);
				//if (j >= 0) {
					//Concept aConcept = aScheme.findAbout_Concept(anEntry.substring(j+1));
				Concept aConcept = aScheme.findAbout_Concept(anEntry.getAbout());
				if (aConcept != null) concepts.add(aConcept);
				//}
			}
		}
		return concepts.toArray(new Concept[0]);
	}

}
